//
// Created by Göksu Güvendiren on 2019-05-14.
//

#include "Scene.hpp"


void Scene::buildBVH() {
    printf(" - Generating BVH...\n\n");
    this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

void Scene::sampleLight(Intersection &pos, float &pdf) const
{
    float emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
        }
    }
    float p = get_random_float() * emit_area_sum;
    emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
            if (p <= emit_area_sum){
                objects[k]->Sample(pos, pdf);
                break;
            }
        }
    }
}

bool Scene::trace(
        const Ray &ray,
        const std::vector<Object*> &objects,
        float &tNear, uint32_t &index, Object **hitObject)
{
    *hitObject = nullptr;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        float tNearK = kInfinity;
        uint32_t indexK;
        Vector2f uvK;
        if (objects[k]->intersect(ray, tNearK, indexK) && tNearK < tNear) {
            *hitObject = objects[k];
            tNear = tNearK;
            index = indexK;
        }
    }


    return (*hitObject != nullptr);
}

/*
shade(p, wo)
    Uniformly sample the light at xx (pdf_light = 1 / A)
    Shoot a ray from p to x
    If the ray is not blocked in the middle
    L_dir = L_i * f_r * cos_theta * cos_theta_x / |x-p|^2/ pdf_light

    L_indir = 0.0
    Test Russian Roulette with probability P_RR
    Uniformly sample the hemisphere toward wi (pdf_hemi = 1/ 2pi)
     Trace a ray r(p, wi)
     If ray r hit a non -emitting object at q
        L_indir = shade(q, wi) * f_r * cos_theta / pdf_hemi /P_RR

    Return L_dir + L_indir
 */

/*
shade(p, wo)
    sampleLight(inter , pdf_light)
    Get x, ws, NN, emit from inter
    Shoot a ray from p to x
    If the ray is not blocked in the middle
    L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws,NN) / |x-p|^2 / pdf_light

    L_indir = 0.0
    Test Russian Roulette with probability RussianRoulette
    wi = sample(wo, N)
    Trace a ray r(p, wi)
    If ray r hit a non -emitting object at q
    L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N)/ pdf(wo, wi, N) / RussianRoulette

    Return L_dir + L_indir
 */

// TODO: Implement Path Tracing Algorithm here
/*
在本次实验中，你只需要修改这一个函数:
• castRay(const Ray ray, int depth)in Scene.cpp: 在其中实现 Path Tracing 算法
可能用到的函数有：
• intersect(const Ray ray)in Scene.cpp: 求一条光线与场景的交点
• sampleLight(Intersection pos, float pdf) in Scene.cpp: 在场景的所有
光源上按面积 uniform 地 sample 一个点，并计算该 sample 的概率密度
• sample(const Vector3f wi, const Vector3f N) in Material.cpp: 按照该
材质的性质，给定入射方向与法向量，用某种分布采样一个出射方向
• pdf(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp: 给定一对入射、出射方向与法向量，计算 sample 方法得到该出射
方向的概率密度
• eval(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp: 给定一对入射、出射方向与法向量，计算这种情况下的 f_r 值
可能用到的变量有：
• RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率
 */

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    Vector3f ldir = {0, 0, 0};
    Vector3f lindir = {0, 0, 0};

    Intersection objectInter = intersect(ray);
    if(!objectInter.happened)
    {
        return {};
    }

    if(objectInter.m->hasEmission())
    {
        return objectInter.m->getEmission();
    }

    Intersection lightInter;
    float lightPdf = 0.0f;
    sampleLight(lightInter, lightPdf);

    Vector3f obj2light = lightInter.coords - objectInter.coords;
    Vector3f obj2lightdir = obj2light.normalized();
    float distancePow2 = obj2light.x * obj2light.x + obj2light.y * obj2light.y + obj2light.z * obj2light.z;

    Ray obj2lightray = {objectInter.coords, obj2lightdir};
    Intersection t = intersect(obj2lightray);
    if(t.distance - obj2light.norm() > -EPSILON)
    {
        ldir = lightInter.emit * objectInter.m->eval(ray.direction, obj2lightdir, objectInter.normal) * dotProduct(obj2lightdir, objectInter.normal) * dotProduct(-obj2lightdir, lightInter.normal) / distancePow2 / lightPdf;
    }

    if(get_random_float() > RussianRoulette)
    {
        return ldir;
    }

    Vector3f obj2nextobjdir = objectInter.m->sample(ray.direction, objectInter.normal).normalized();
    Ray obj2nextobjray = {objectInter.coords, obj2nextobjdir};
    Intersection nextObjInter = intersect(obj2nextobjray);
    if(nextObjInter.happened && !nextObjInter.m->hasEmission())
    {
        float pdf = objectInter.m->pdf(ray.direction, obj2nextobjdir, objectInter.normal);
        lindir = castRay(obj2nextobjray, depth + 1) * objectInter.m->eval(ray.direction, obj2nextobjdir, objectInter.normal) * dotProduct(obj2nextobjdir, objectInter.normal) / pdf / RussianRoulette;
    }

    return ldir + lindir;
//    Intersection inter = intersect(ray);
//    if (!inter.happened) {
//        return Vector3f(0.0f, 0.0f, 0.0f);
//    }
//    if (inter.m->hasEmission()) {
//        return inter.m->getEmission();
//    }
//    // 间接光打到光源的时候
//    Intersection lightInter;
//    float lightPdf;
//    sampleLight(lightInter, lightPdf);
//    Vector3f &posXPrim = lightInter.coords;
//
//    // 从渲染点到光源点
//    Vector3f p2Light = lightInter.coords - inter.coords;
//    Ray p2LightRay(inter.coords, p2Light);
//    Intersection blockedInter = intersect(p2LightRay);
//    // 我们认为所有方向都从P点朝外
//    Vector3f shaderPointWo = normalize(-ray.direction);
//    Vector3f shaderPointWi = normalize(p2Light);
//    Vector3f L_dir;
//    if ((blockedInter.coords - lightInter.coords).norm() <= EPSILON ) {
//        Vector3f shaderNormal = inter.normal.normalized();
//        Vector3f lightNormal = lightInter.normal.normalized();
//        float distancePow2 = p2Light.x * p2Light.x + p2Light.y * p2Light.y + p2Light.z * p2Light.z;
//        L_dir = lightInter.emit * inter.m->eval(shaderPointWi, shaderPointWo, inter.normal)
//                * dotProduct(shaderNormal, shaderPointWi) * dotProduct(lightNormal, -shaderPointWo)
//                / distancePow2 / lightPdf;
//    }
//    Vector3f L_indir;
//    if(get_random_float() > RussianRoulette)
//    {
//        return L_dir;
//    }
//    Vector3f p2NextDir = inter.m->sample(shaderPointWo, inter.normal).normalized();
//    Ray p2NextRay(inter.coords, p2NextDir);
//    Intersection nextInter = intersect(p2NextRay);
//    if (!nextInter.happened) {  // 没有发生反射，所以间接光为0
//        return L_dir;
//    }
//    if (nextInter.m->hasEmission()) {   // 老师特意提醒的，遇到光源就讲间接光设置为0.0f
//        return L_dir;
//    }
//    float pdf = nextInter.m->pdf(p2NextDir, shaderPointWo, inter.normal);
//    L_indir = castRay(p2NextRay, depth+1) * inter.m->eval(p2NextDir, shaderPointWo, inter.normal)
//            * dotProduct(p2NextDir, inter.normal)/pdf/RussianRoulette;
//    return L_dir + L_indir;
}